defmodule PangoMqttClient do
	@moduledoc false
	use ExActor.GenServer
	alias Tortoise.Connection, as: MqttConn
	require Logger

	@reconnect_interval 3_000
	@pool_name :mqtt_pool

	@doc """
	config: %{
		"topic" => "",
		:user_name => ""
		:password => ""
		:host => ""
		:port => integer,
	}
	"""
	@spec children(integer, integer, keyword, atom) :: list
	def children(pool_size, max_overflow, config, handler) do
		PangoPoolLib.children(@pool_name, __MODULE__, pool_size, max_overflow, [config, handler])
	end

	def publish(topic, msg) do
		:poolboy.transaction(
			@pool_name,
			fn (worker) ->
				__MODULE__.publish(worker, topic, msg)
			end,
			5000
		)
	end

	def publish_sync(topic, msg) do
		:poolboy.transaction(
			@pool_name,
			fn (worker) ->
				__MODULE__.publish_sync(worker, topic, msg)
			end,
			5000
		)
	end

	defstart start_link([config, handler]) do
		client_id = config[:user_name] <> "@" <> inspect(self())
		:timer.apply_after(@reconnect_interval, __MODULE__, :connect, [self()])
		initial_state(
			%{
				config: config,
				connect: nil,
				connecting: true,
				client_id: client_id,
				handler: handler
			}
		)
	end

	defcast sub(topic), state: state do
		%{client_id: client_id} = state
		Tortoise.Connection.subscribe(client_id, {topic, 0})
		noreply()
	end

	defcast unsub(topic), state: state do
		%{client_id: client_id} = state
		Tortoise.Connection.unsubscribe(client_id, topic)
		noreply()
	end


	defcast connect,
	        state: %{
		        config: config,
		        connect: nil,
		        client_id: client_id,
		        handler: handler
	        } do
		raw_pass = config[:password]

		{rslt, connecting} =
			case MqttConn.start_link(
				     client_id: client_id,
				     server: {
					     Tortoise.Transport.Tcp,
					     host: config[:host],
					     port: config[:port]
				     },
				     handler: {handler, [self(), __MODULE__]},
				     keep_alive: 120,
				     user_name: config[:user_name],
				     password: raw_pass
			     ) do
				{:ok, pid} ->
					Logger.debug("mqtt client connect ok")
					{pid, false}

				err ->
					Logger.warn("connect failed reconnect after 3 secs #{err}")
					:timer.apply_after(@reconnect_interval, __MODULE__, :connect, [self()])
					{nil, true}
			end

		new_state(
			%{
				config: config,
				connect: rslt,
				connecting: connecting,
				client_id: client_id,
				handler: handler
			}
		)
	end
	defcast publish(topic, binary_msg, qos \\ 1),
	        state: %{
		        connect: _conn,
		        client_id: cid,
	        } do
		Tortoise.publish(cid, topic, binary_msg, qos: qos)
		noreply()
	end

	defcall publish_sync(topic, binary_msg, qos \\ 1),
	        state: %{
		        connect: _conn,
		        client_id: cid,
	        } do

		case Tortoise.publish_sync(cid, topic, binary_msg, qos: qos) do
			:ok ->
				reply(:ok)
			{:error, :timeout} ->
				reply(:timeout)
		end
	end

	def handle_info({{Tortoise, _}, _, :ok}, state) do
		{:noreply, state}
	end
	def handle_info(msg, state) do
		Logger.debug("handle_info, msg: #{inspect(msg)}")
		{:noreply, state}
	end

	defcast connection_terminated, state: %{connecting: _connecting} = _state do
		Logger.warn("connection terminated")
		stop_server("")
	end

end
